#include "CurveFitting.hpp"
#include <vector>
#include <cmath>
#include <iostream>
#include <fstream>
#include <utility>

const double PI = acos(-1.); // Define PI

// Define the R1 curve functions
class R1X : public Function {
public:
    double operator()(double t) const override {
        return sqrt(3) * cos(t); // x-coordinate
    }
};

class R1Y : public Function {
public:
    double operator()(double t) const override {
        double x = sqrt(3) * cos(t);
        return (2.0 / 3.0) * sqrt(fabs(x)) + (2.0 / 3.0) * sqrt(3) * sin(t); // y-coordinate
    }
};

// Define the R2 curve functions
class R2X : public Function {
public:
    double operator()(double t) const override {
        return sin(t) + t * cos(t);
    }
};

class R2Y : public Function {
public:
    double operator()(double t) const override {
        return cos(t) - t * sin(t);
    }
};

void outputCurvesToFile(const PlaneCurveBezierFitting& bezierFitting, const PlaneCurveCubicSplineFitting& splineFitting, const std::string& filename) {
    std::ofstream outfile(filename);

    double t_min = bezierFitting.getTMin();
    double t_max = bezierFitting.getTMax();
    for (double t = t_min; t <= t_max; t += (t_max - t_min) / 500) {
        std::pair<double, double> bezierPoint = bezierFitting.evaluate(t);
        outfile << bezierPoint.first << " " << bezierPoint.second << std::endl;
    }
    
    outfile << std::endl;

    for (double t = t_min; t <= t_max; t += (t_max - t_min) / 500) {
        std::pair<double, double> splinePoint = splineFitting.evaluate(t);
        outfile << splinePoint.first << " " << splinePoint.second << std::endl;
    }
    outfile.close();
}

int main() {
    int values_of_m[] = {10, 40, 160}; // Different subdivision values for curve segments

    const std::string output_dir = "output";

    R1X xFunc;
    R1Y yFunc;
    PlaneCurve r1Curve(&xFunc, &yFunc, 0, 2 * PI);

    R2X r2xFunc;
    R2Y r2yFunc;
    PlaneCurve r2Curve(&r2xFunc, &r2yFunc, 0, 6 * PI);

    // Loop over different values of m to generate curves with varying levels of detail
    for (int m : values_of_m) {
        // Equal spacing
        PlaneCurveBezierFitting r1FittingEqualBezier(r1Curve, m, false);
        PlaneCurveCubicSplineFitting r1FittingEqualCubic(r1Curve, m);
        std::string r1FilenameEqual = output_dir + "/E_R1_Equal_" + std::to_string(m) + ".txt";
        outputCurvesToFile(r1FittingEqualBezier, r1FittingEqualCubic, r1FilenameEqual);

        PlaneCurveBezierFitting r2FittingEqualBezier(r2Curve, m, false);
        PlaneCurveCubicSplineFitting r2FittingEqualCubic(r2Curve, m);
        std::string r2FilenameEqual = output_dir + "/E_R2_Equal_" + std::to_string(m) + ".txt";
        outputCurvesToFile(r2FittingEqualBezier, r2FittingEqualCubic, r2FilenameEqual);

        // Chordal length spacing
        PlaneCurveBezierFitting r1FittingChordalBezier(r1Curve, m, true);
        PlaneCurveCubicSplineFitting r1FittingChordalCubic(r1Curve, m);
        std::string r1FilenameChordal = output_dir + "/E_R1_Chordal_" + std::to_string(m) + ".txt";
        outputCurvesToFile(r1FittingChordalBezier, r1FittingChordalCubic, r1FilenameChordal);

        PlaneCurveBezierFitting r2FittingChordalBezier(r2Curve, m, true);
        PlaneCurveCubicSplineFitting r2FittingChordalCubic(r2Curve, m);
        std::string r2FilenameChordal = output_dir + "/E_R2_Chordal_" + std::to_string(m) + ".txt";
        outputCurvesToFile(r2FittingChordalBezier, r2FittingChordalCubic, r2FilenameChordal);
    }

    return 0;
}


